package org.xx.armory.swing;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.commons.PathInputStream;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.WeakHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 资源加载器。
 * <p>根据指定的区域修正文件名并且加载文件的内容</p>
 */
public final class ResourceLoader {
    private final Logger logger = LoggerFactory.getLogger(ResourceLoader.class);

    private Locale locale;

    private WeakHashMap<String, Image> imagesCache;

    private ResourceLoader(
            Locale locale
    ) {
        this.locale = notNull(locale, "locale");
        this.imagesCache = new WeakHashMap<>();
    }

    /**
     * 获取当前系统区域的资源加载器。
     *
     * @return 当前系统区域的资源加载器。
     */
    public static ResourceLoader getInstance() {
        return getInstance(Locale.getDefault());
    }

    /**
     * 获取指定区域的资源加载器。
     *
     * @param localeID
     *         区域名称。
     * @return 指定区域的系统加载器。
     * @throws IllegalArgumentException
     *         如果参数{@code localeID}是{@code null}或者只包含空白字符。
     */
    public static ResourceLoader getInstance(
            String localeID
    ) {
        localeID = notBlank(localeID, "locale").trim();

        final Locale locale;

        int pos = localeID.charAt('_');
        if (pos > 0) {
            locale = new Locale(left(localeID, pos), substring(localeID, pos + 1));
        } else {
            locale = new Locale(localeID);
        }

        return getInstance(locale);
    }

    /**
     * 获取指定区域的资源加载器。
     *
     * @param locale
     *         区域。
     * @return 指定区域的系统加载器。
     * @throws IllegalArgumentException
     *         如果参数{@code localeID}是{@code null}。
     */
    public static ResourceLoader getInstance(
            Locale locale
    ) {
        notNull(locale, "locale");

        return new ResourceLoader(locale);
    }

    public final InputStream loadStream(
            String name
    )
            throws IOException {
        return load(name);
    }

    /**
     * 加载文本文件。
     *
     * @param name
     *         文本文件的名字。
     * @return 已加载的文本文件Reader，如果指定的文件不存在或者无法读取则返回长度为0的Reader。
     */
    public final Reader loadText(
            String name
    ) {
        try {
            return new InputStreamReader(new BufferedInputStream(load(name)), UTF_8);
        } catch (IOException ex) {
            return new StringReader("");
        }
    }

    /**
     * 加载图像。
     *
     * @param name
     *         图像文件的名字。
     * @return 已加载的图像，如果指定的文件不存在或者无法读取为图像则返回{@code null}。
     */
    public final Image loadImage(
            String name
    ) {
        return loadImage(null, name);
    }

    /**
     * 加载图像。
     *
     * @param refer
     *         引用此图像的文件的名字。
     * @param src
     *         图像的文件名。
     * @return 已加载的图像，如果指定的文件不存在或者无法读取为图像则返回{@code null}。
     */
    public final Image loadImage(
            String refer,
            String src
    ) {
        refer = trimToEmpty(refer);
        src = notBlank(src, "name").trim();

        final Path p = Paths.get(src);
        if (!p.isAbsolute() && !refer.isEmpty()) {
            // 如果图像文件名是相对路径，并且指定了引用此图像的资源名，那么使用引用名来调整图像文件名。
            final Path p2 = Paths.get(refer).resolveSibling(p);

            logger.trace("refer: {}, src: {}, result: {}", refer, src, p2);

            src = p2.toString();
        }

        return imagesCache.computeIfAbsent(src, this::loadImageByName);
    }

    private Image loadImageByName(
            String name
    ) {
        try {
            final ImageInputStream stream = new MemoryCacheImageInputStream(load(name));
            return ImageIO.read(stream);
        } catch (IOException ex) {
            logger.warn("cannot load image: " + name, ex);
            return null;
        }
    }

    /**
     * 加载InputStream。
     *
     * @param name
     *         文件的名字，如果是绝对路径则直接读取，否则看作相对于类路径的名字。
     * @return 已加载的文件。
     * @throws IOException
     *         如果指定的文件不存在或者无法读取内容。
     */
    private InputStream load(
            String name
    )
            throws IOException {
        name = notBlank(name, "name").trim();

        final File file = new File(name);
        if (file.isAbsolute()) {
            return loadAbsolute(name);
        } else {
            return loadResource(name);
        }
    }

    private InputStream loadAbsolute(
            String name
    )
            throws IOException {
        name = notBlank(name, "name").trim();

        final String path = FilenameUtils.getFullPathNoEndSeparator(name);
        final String baseName = FilenameUtils.getBaseName(name);
        final String extension = FilenameUtils.getExtension(name);
        final String baseName1 = baseName + "_" + this.locale.getLanguage();
        final String baseName2 = baseName + "_" + this.locale.getLanguage() + "_" + this.locale.getCountry();

        File file = new File(concatName(path, File.separatorChar, baseName2, extension));
        if (!file.isFile()) {
            file = new File(concatName(path, File.separatorChar, baseName1, extension));
        }
        if (!file.isFile()) {
            file = new File(concatName(path, File.separatorChar, baseName, extension));
        }
        if (!file.isFile()) {
            throw new IOException("cannot open file: " + file);
        }

        return new PathInputStream(file);
    }

    private InputStream loadResource(
            String name
    )
            throws IOException {
        name = notBlank(name, "name").trim();

        final String path = FilenameUtils.getFullPathNoEndSeparator(name);
        final String baseName = FilenameUtils.getBaseName(name);
        final String extension = FilenameUtils.getExtension(name);
        final String baseName1 = baseName + "_" + this.locale.getLanguage();
        final String baseName2 = baseName + "_" + this.locale.getLanguage() + "_" + this.locale.getCountry();

        InputStream stream;

        name = concatName(path, '/', baseName2, extension);
        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
        stream = loader.getResourceAsStream(name);
        if (stream == null) {
            name = concatName(path, '/', baseName1, extension);
            stream = loader.getResourceAsStream(name);
        }
        if (stream == null) {
            name = concatName(path, '/', baseName, extension);
            stream = loader.getResourceAsStream(name);
        }
        if (stream == null) {
            throw new IOException("cannot open resource: " + name);
        }

        return new PathInputStream(name, stream);
    }

    private String concatName(
            String path,
            char separatorChar,
            String baseName,
            String extension
    ) {
        final StringBuilder result = new StringBuilder();

        if (!isBlank(path)) {
            result.append(path).append(separatorChar);
        }

        result.append(baseName);

        if (!isBlank(extension)) {
            result.append('.').append(extension);
        }

        return result.toString();
    }
}
